// android does not support openal but most other operating systems do
#ifndef ANDROID_PLATFORM

#include "VxIrrLib.h"
#include "stb_vorbis.h"

//============================================================================
//=== audio source ===//
//============================================================================

//============================================================================
//=== constructor ===//
AudioSource::AudioSource(	const AudioBuffer * poAudioBuffer, 
							BOOL	bLoop, 
							F32		f32MinGain, 
							F32		f32MaxGain, 
							F32		f32ReferenceDistance, 
							F32		f32RollOff  )
{
	m_bLoaded = false;
	if( poAudioBuffer ) 
	{

		// generate id
		alGenSources( 1, &m_u32ID );

		// Assign buffer to source
		alSourcei( m_u32ID, AL_BUFFER, poAudioBuffer-> m_u32ID );
		alSourcef( m_u32ID, AL_MIN_GAIN, f32MinGain );
		alSourcef( m_u32ID, AL_MAX_GAIN, f32MaxGain );
		alSourcef( m_u32ID, AL_REFERENCE_DISTANCE, f32ReferenceDistance );
		alSourcef( m_u32ID, AL_ROLLOFF_FACTOR, f32RollOff );
		alSourcei( m_u32ID, AL_LOOPING, bLoop);
		m_bLoaded = true;
	}
}
//============================================================================
//=== destructor ===//
AudioSource::~AudioSource()
{
	if( m_bLoaded ) 
	{
		alDeleteSources( 1, &m_u32ID );
		m_bLoaded = false;
	}
}

//============================================================================
void AudioSource::SetGain( F32 f32Value )
{
	if( m_bLoaded ) 
	{
		alSourcef( m_u32ID, AL_GAIN, f32Value );
	}
}
//============================================================================
void AudioSource::SetPosition( F32 f32X, F32 f32Y, F32 f32Z )
{
	if( m_bLoaded ) 
	{
		alSource3f( m_u32ID, AL_POSITION, f32X, f32Y, -f32Z );
	}
}
//============================================================================
void AudioSource::SetPitch( F32 f32Value )
{
	if( m_bLoaded ) 
	{
		alSourcef( m_u32ID, AL_PITCH, f32Value );
	}
}
//============================================================================
void AudioSource::Play( void )
{
	if( m_bLoaded ) 
	{
		// get state
		ALint iState;
		alGetSourcei( m_u32ID, AL_SOURCE_STATE, &iState );
		if( AL_PLAYING == iState )
		{
			// stop playing before we start playing again
			alSourceStop( m_u32ID );
		}
		
		// play sound
		alSourcePlay( m_u32ID );
	}
}

//============================================================================
//=== audio base ===//
//============================================================================
//============================================================================
//=== constructor ===//
AudioBase::AudioBase()
	: m_bEnabled(0)
{
}
//============================================================================
//=== destructor ===//
AudioBase::~AudioBase()
{
}

//============================================================================
//! startup
RCODE AudioBase::Startup( BOOL bEnabled )
{
	m_bEnabled = bEnabled;
	if( false == m_bEnabled )
	{
		return -1;
	}

	// open openal audio device
	ALCdevice * poSndDevice = alcOpenDevice(NULL);
	if( NULL == poSndDevice ) 
	{
		// probably no sound card in system
		log_msg( 0, "AudioBase::Startup: FAILED to create audio device");
		m_bEnabled = false;
		return -1;
	}

	// initialize openal
	ALCcontext * poSndContext = alcCreateContext( poSndDevice, NULL);
	alcMakeContextCurrent( poSndContext );
	alGetError();
	return 0;
}
//============================================================================
//! shutdown
void AudioBase::Shutdown( void )
{
	if( false == m_bEnabled )
	{
		return;
	}

	FreeAllBuffers();
	
	// shutdown openal
	ALCcontext * poSndContext = alcGetCurrentContext();
	ALCdevice * poSndDevice = alcGetContextsDevice( poSndContext );
	alcMakeContextCurrent(NULL);
	alcDestroyContext( poSndContext );
	alcCloseDevice( poSndDevice );
}


//============================================================================
//=== methods ===//
//============================================================================
//============================================================================
//! load audio buffer from sound file
RCODE AudioBase::LoadBuffer( const irr::core::stringc& strFile )
{
	if( false == m_bEnabled )
	{
		return -1;
	}
	irr::core::stringc strPathAndName = GetAssetPathAndName( strFile.c_str(), eGameAssetTypeSound );
	// see if buffer allready loaded
	if( m_aoAudioBuffers.find( strPathAndName ) != m_aoAudioBuffers.end())
	{
		// allready loaded... is ok
		return 0;
	}
	//TODO if other than ogg file load with different loader
	return LoadVorbisFile( strPathAndName );
}

//============================================================================
//! load vorbis ogg file
RCODE AudioBase::LoadVorbisFile( irr::core::stringc& strPathAndFile )
{
	// load file into buffer
	stb_vorbis * poVorbisStream = stb_vorbis_open_filename((char*)strPathAndFile.c_str(), NULL, NULL);
	if( NULL == poVorbisStream ) 
	{
		log_msg( 0, "AudioBase::LoadBuffer: ERROR loading file %s", strPathAndFile.c_str() );
		return -1;
	}

	stb_vorbis_info oVorbisInfo = stb_vorbis_get_info( poVorbisStream );
	AudioBuffer oAudioBuffer;
	switch( oVorbisInfo.channels ) 
	{
	case 1:
		oAudioBuffer.m_eFormat = AL_FORMAT_MONO16;
		break;
	case 2:
		oAudioBuffer.m_eFormat = AL_FORMAT_STEREO16;
		break;
	default:
		log_msg( 0, "AudioBase::LoadBuffer: UNKNOWN FORMAT in file %s", strPathAndFile.c_str() );
		return -1;
	}

	// get sample count
	unsigned int uiSampleCount = stb_vorbis_stream_length_in_samples( poVorbisStream ) * oVorbisInfo.channels;

	// allocate memory for the samples
	ALshort * ps16SndData = new ALshort[ uiSampleCount ];

	// read samples from ogg file
	int iReadSampleCnt = stb_vorbis_get_samples_short_interleaved( poVorbisStream, oVorbisInfo.channels, ps16SndData, (int)uiSampleCount );

	// generate audio buffer ( openal will assign id )
	alGenBuffers( 1, &oAudioBuffer.m_u32ID );
	alBufferData( oAudioBuffer.m_u32ID, oAudioBuffer.m_eFormat, ps16SndData, iReadSampleCnt * sizeof(ALshort), oVorbisInfo.sample_rate );

	// free memory ( openal has copy )
	stb_vorbis_close( poVorbisStream );
	delete ps16SndData;

	// put buffer info into mapped pair ( path, buffer ) 
	m_aoAudioBuffers[ strPathAndFile ] = oAudioBuffer;
	return true;

}
//============================================================================
//! get audio buffer that is loaded from sound file
const AudioBuffer *	 AudioBase::GetBuffer( const irr::core::stringc & strFile)
{
	if( false == m_bEnabled )
	{
		return NULL;
	}
	irr::core::stringc strPathAndName = GetAssetPathAndName( strFile.c_str() );

	// find buffer
	AudioBuffersIterator = m_aoAudioBuffers.find( strPathAndName );
	if( AudioBuffersIterator == m_aoAudioBuffers.end() )
	{
		// no buffer with given path and file exists in our list
		// attempt to load
		log_msg( 1, "AudioBase::GetBuffer: could not find %s.. attempting load\n", strFile.c_str() );
		RCODE rc = LoadBuffer( strFile );
		if( rc )
		{
			log_msg( 1, "AudioBase::GetBuffer: load ERROR %d\n", rc );
			return NULL;
		}
		return NULL;
	}
	return &AudioBuffersIterator->second;
}
//============================================================================
//! free all sound buffers
void AudioBase::FreeAllBuffers( void )
{
	if( false == m_bEnabled )
	{
		return;
	}
	for( AudioBuffersIterator = m_aoAudioBuffers.begin(); AudioBuffersIterator != m_aoAudioBuffers.end(); ++AudioBuffersIterator ) 
	{
		AudioBuffer& oAudioBuffer = AudioBuffersIterator->second;
		alDeleteBuffers( 1, &oAudioBuffer.m_u32ID );
	}
	m_aoAudioBuffers.clear();
}
//============================================================================
//! set position of sound listener
void AudioBase::SetListenPosition( F32 f32X, F32 f32Y, F32 f32Z )
{
	if( false == m_bEnabled )
	{
		return;
	}
	alListener3f( AL_POSITION, f32X, f32Y, -f32Z);
}
//============================================================================
//! set position of sound listener ( irrlicht coords )
void AudioBase::SetListenPosition( irr::core::vector3df& v3fPos )
{
	if( false == m_bEnabled )
	{
		return;
	}
	alListener3f( AL_POSITION, v3fPos.X, v3fPos.Y, -v3fPos.Z );
}
//============================================================================
//! set position of sound listener ( bullet physics coords ) 
void AudioBase::SetListenPosition( btVector3& vec3Pos )
{
	if( false == m_bEnabled )
	{
		return;
	}
	alListener3f( AL_POSITION, vec3Pos[0], vec3Pos[1], -vec3Pos[2] );
}
//============================================================================
//! set direction listener if facing
void AudioBase::SetListenDirection( F32 f32X, F32 f32Y, F32 f32Z )
{
	if( false == m_bEnabled )
	{
		return;
	}
	F32 af32Orientation[6] = { f32X, f32Y, -f32Z, 0.0f, 1.0f, 0.0f };
	alListenerfv( AL_ORIENTATION, af32Orientation );
}
//============================================================================
//! set direction of sound listener ( irrlicht coords )
void AudioBase::SetListenDirection( irr::core::vector3df& v3fDir )
{
	if( false == m_bEnabled )
	{
		return;
	}
	F32 af32Orientation[6] = { v3fDir.X, v3fDir.Y, -v3fDir.Z, 0.0f, 1.0f, 0.0f };
	alListenerfv( AL_ORIENTATION, af32Orientation );
}
//============================================================================
//! set direction of sound listener ( bullet physics coords ) 
void AudioBase::SetListenDirection( btVector3& vec3Dir )
{
	if( false == m_bEnabled )
	{
		return;
	}
	F32 af32Orientation[6] = { vec3Dir[0], vec3Dir[1], -vec3Dir[2], 0.0f, 1.0f, 0.0f };
	alListenerfv( AL_ORIENTATION, af32Orientation );
}

#endif // ANDROID_PLATFORM
